package com.mai4j.maiadmin.service.impl;

import com.google.gson.Gson;
import com.google.gson.reflect.TypeToken;
import com.mai4j.maiadmin.exception.UserAuthenticationException;
import com.mai4j.maiadmin.misc.Password;
import com.mai4j.maiadmin.model.RespCode;
import com.mai4j.maiadmin.model.dto.SignInDTO;
import com.mai4j.maiadmin.model.dto.UserInfoDTO;
import com.mai4j.maiadmin.service.AuthenticationService;
import com.mai4j.maiadmin.support.CtxProvider;
import com.mai4j.maiadmin.support.JooqProvider;
import lombok.extern.slf4j.Slf4j;
import org.jooq.Record;
import org.springframework.cache.annotation.Cacheable;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.time.LocalDateTime;
import java.util.HashSet;
import java.util.Objects;
import java.util.Set;

import static com.mai4j.maiadmin.generated.Tables.*;

@Service
@Slf4j
public class AuthenticationServiceImpl implements AuthenticationService {

    @Transactional
    @Override
    public UserInfoDTO signIn(SignInDTO signInDTO) {
        Record record = JooqProvider.dsl()
                .select(
                        USER.USER_ID,
                        USER.USER_GROUP_ID,
                        USER.ACCOUNT,
                        USER.NAME,
                        USER.EMAIL,
                        USER.TELEPHONE,
                        USER.IP,
                        USER.GENDER,
                        USER.STATUS,
                        USER.AVATAR,
                        USER_PASSWORD_AUTH.PASSWORD,
                        USER_PASSWORD_AUTH.SALT
                )
                .from(USER_PASSWORD_AUTH)
                .innerJoin(USER)
                .on(USER_PASSWORD_AUTH.USER_ID.eq(USER.USER_ID))
                .where(USER.ACCOUNT.eq(signInDTO.getIdentity())
                        .or(USER.EMAIL.eq(signInDTO.getIdentity())) // 注意 account 和 email的合集也需要唯一，注册时限制
                ).fetchOne();
        if (record != null) {
            long userId = record.get(USER.USER_ID);
            String salt = record.get(USER_PASSWORD_AUTH.SALT);
            String password = Password.password(signInDTO.getPassword(), salt);
            if (Objects.equals(record.get(USER_PASSWORD_AUTH.PASSWORD), password)) {
                JooqProvider.dsl()
                        .update(USER)
                        .set(USER.DATE_LAST, LocalDateTime.now())
                        .where(USER.USER_ID.eq(userId)).execute();
                Long userGroupId = record.get(USER.USER_GROUP_ID);
                AuthenticationService authenticationService = CtxProvider.getBean(AuthenticationService.class);
                return new UserInfoDTO()
                        .setUserId(userId)
                        .setUserGroupId(userGroupId)
                        .setAccount(record.get(USER.ACCOUNT))
                        .setName(record.get(USER.NAME))
                        .setEmail(record.get(USER.EMAIL))
                        .setTelephone(record.get(USER.TELEPHONE))
                        .setIp(record.get(USER.IP))
                        .setGender(record.get(USER.GENDER))
                        .setStatus(record.get(USER.STATUS))
                        .setAvatar(record.get(USER.AVATAR))
                        .setAuthorities(authenticationService.getAuthorities(userGroupId)); // 内部
            } else {
                throw new UserAuthenticationException(RespCode.SIGN_IN_FAIL, RespCode.SIGN_IN_FAIL_PW_INCORRECT, "text.password.incorrect");
            }
        } else {
            throw new UserAuthenticationException(RespCode.SIGN_IN_FAIL, RespCode.SIGN_IN_FAIL_ACCOUNT_NOT_EXISTS, "text.user.!exists");
        }
    }

    @Override
    @Cacheable(value = "data", key = "'authorities:' + #userGroupId")
    public Set<String> getAuthorities(Long userGroupId) {
        Set<String> authorities = JooqProvider.dsl().select(USER_GROUP.PERMISSION)
                .from(USER_GROUP)
                .where(USER_GROUP.USER_GROUP_ID.eq(userGroupId)).fetchOne(record -> {
                    String permission = record.get(USER_GROUP.PERMISSION);
                    return new Gson().fromJson(permission,
                            new TypeToken<HashSet<String>>() {
                            }.getType());
                });
        return authorities == null ? new HashSet<>() : authorities;
    }
}
